from django.shortcuts import render
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views import View
from django_redis import get_redis_connection
from decimal import Decimal # 定义高精度的浮点数,一般用于定义金钱. 1.2 != 1.199999999999
# 计算机可能有精度缺失
import json
from django import http
from django.utils import timezone
from django.db import transaction

from meiduo_mall.utils.views import LoginRequiredJSONMixin
from apps.goods.models import SKU
from apps.users.models import Address
from apps.orders.models import OrderInfo,OrderGoods
from meiduo_mall.utils.response_code import RETCODE
# Create your views here.



#########提交订单成功页面################
class OrderSuccessView(LoginRequiredMixin, View):
    """提交订单成功"""

    def get(self, request):
        """提供提交订单成功页面"""
        order_id = request.GET.get('order_id')
        payment_amount = request.GET.get('payment_amount')
        pay_method = request.GET.get('pay_method')

        context = {
            'order_id':order_id,
            'payment_amount':payment_amount,
            'pay_method':pay_method
        }
        return render(request, 'order_success.html', context)



##########提交订单###################
class OrderCommitView(LoginRequiredJSONMixin, View):
    """订单提交"""

    def post(self, request):
        """保存订单信息和订单商品信息"""
        # 接收参数
        json_dict = json.loads(request.body.decode())
        address_id = json_dict.get("address_id")
        pay_method = json_dict.get("pay_method")

        # 校验参数
        if not all([address_id,pay_method]):
            return http.HttpResponseForbidden("缺少必传参数")
        try:
            address = Address.objects.get(id=address_id)
        except Address.DoesNotExist:
            return http.HttpResponseForbidden("参数address_id错误")
        # 判断支付方式是否在规定范围内
        # if pay_method not in [1 ,2]:
        if pay_method not in [OrderInfo.PAY_METHODS_ENUM["CASH"],
                              OrderInfo.PAY_METHODS_ENUM["ALIPAY"]]:
            return http.HttpResponseForbidden("参数pay_method错误")

        # 开启事务的上下文: 显示的开启一次事务
        with transaction.atomic():
            # 创建保存点: 用于回滚和提交de
            save_id = transaction.savepoint()

            # 暴力回滚
            try:
                # 保存订单基本信息(OrderInfo(一)
                # 生成订单号:
                user = request.user
                order_id = timezone.localtime().strftime("%Y%m%d%H%M%S") +("%09d" %user.id)

                order = OrderInfo.objects.create(
                    order_id= order_id,
                    user = user,
                    address = address,
                    total_count = 0,
                    total_amount = Decimal(0.00),
                    freight = Decimal(10.00),
                    pay_method = pay_method,
                    # status = "待发货" if pay_method == "货到付款" else "待支付",
                    # status = 2 if pay_method == 1 else 1, #可读性差
                    status = OrderInfo.ORDER_STATUS_ENUM["UNSEND"] if pay_method == OrderInfo.PAY_METHODS_ENUM["CASH"] else OrderInfo.ORDER_STATUS_ENUM["UNPAID"],
                )

                # 从redis读取购物车被勾选的商品信息
                # 创建连接redis的4号库的对象
                redis_conn = get_redis_connection("carts")
                # 查询hash中sku_id和count的数据
                redis_cart = redis_conn.hgetall("carts_%s" % user.id)
                # 查询set中所有的sku_id
                redis_selected = redis_conn.smembers("selected_%s" % user.id)

                # 遍历set中所有的sku_id,通过被勾选的商品的sku_id构造新的被勾选的购物车商品
                new_cart_dict = {}  # {"sku_id1": "1"}
                for sku_id in redis_selected:
                    new_cart_dict[int(sku_id)] = int(redis_cart[sku_id])

                # 遍历购物车中被勾选的商品信息
                sku_ids = new_cart_dict.keys()
                # 这里只能这么查，不能使用filter(id__in=sku_ids,因为有查询集缓存,对将来跟新库存和销量不利
                for sku_id in sku_ids:
                    # 循环的给这个商品下单,直到库存不足
                    while True:
                        # 查询SKU信息
                        sku = SKU.objects.get(id=sku_id)
                        # 获取原始库存和销量
                        origin_stock = sku.stock
                        origin_sales = sku.sales

                        # 判断sku库存: 用户要购买的商品数量和商品实时库存做对比
                        sku_count = new_cart_dict[sku.id]
                        if sku_count > origin_stock:
                            # 回滚到保存点
                            transaction.savepoint_rollback(save_id)
                            return http.JsonResponse({"code":RETCODE.STOCKERR,"errmsg":"库存不足"})

                        # 模拟网络延时,没有实际意义,治为了放大错误效果
                        import time
                        time.sleep(10)

                        # SKU减少库存,增加销量
                        # sku.stock -= sku_count
                        # sku.sales += sku_count
                        # sku.save()

                        # 使用乐观锁,来减库存,增加销量
                        new_stock = origin_stock - sku_count
                        new_sales = origin_sales + sku_count
                        result = SKU.objects.filter(id=sku_id, stock=origin_stock).update(stock=new_stock,
                                                                                          sales = new_sales)
                        # 如果乐观锁发现原始库存被别人改动,就会返回0,反之继续执行update
                        if result == 0:
                            # 这里下单失败,仅仅是因为库存被别人改了,但是不代表库存不满足购买量,所以需要继续循环购买,直到库存不足为止
                            # return http.JsonResponse({"code": RETCODE.STOCKERR, "errmsg": "库存不足"})
                            continue

                            """
                            用户要买1,但是库存100,
                            用户购买,网络延迟严重,库存被别人先改了,剩下99个,按照现在的逻辑,下单失败
                            这个逻辑不对

                            购买成功,必须同时满足的两个条件:
                                1. 库存满足购买量
                                2. 库存没有被别人修改
                            """
                        # 修改SPU销量
                        sku.spu.sales += sku_count
                        sku.spu.save()

                        # 保存订单商品信息 OrderGoods(多)
                        OrderGoods.objects.create(
                            order = order,
                            sku = sku,
                            count = sku_count,
                            price = sku.price,
                        )

                        # 保存商品订单中总价和总数量
                        order.total_count += sku_count
                        order.total_amount += sku.price * sku_count
                        # order.save() # 可写可不写

                        # 如果下单成功,要退出循环,必须加,不然最后一定会报库存不足
                        break

                # 给总价添加邮费和保存订单信息
                order.total_amount += order.freight
                order.save()
            except Exception:
                transaction.savepoint_rollback(save_id)
                return http.JsonResponse({"code":RETCODE.DBERR,"errmsg":"下单失败"})

            # 如果操作成功, 显示的提交一次事务
            transaction.savepoint_commit(save_id)

        # 清空购物车中已结算的商品(暂缓),自己写的
        pl = redis_conn.pipeline()
        pl.hdel("carts_%s" % user.id, *redis_selected)
        pl.srem("selected_%s" % user.id, *redis_selected)
        pl.execute()

        # 响应提交订单结果
        return http.JsonResponse({"code":RETCODE.OK,"errmsg":"下单成功","order_id":order_id})




#############结算订单#################
class OrderSettlementView(LoginRequiredMixin, View):
    """结算订单"""

    def get(self, request):
        """提供订单结算页面"""
        # 查询当前登陆用户的未被逻辑删除的收货地址信息(一查多,一方模型类.多方模型类名_set/related_name)
        user = request.user
        try:
            addresses = user.addresses.filter(is_deleted=False)
        except Exception:
            # 如果没有地址,不响应错误信息,如果没有地址,我们可以提示用户去编辑地址
            addresses = None

        # 查询当前登陆用户的被勾选的购物车数据
        # 创建连接redis的4号库的对象
        redis_conn = get_redis_connection("carts")
        # 查询hash中sku_id和count的数据
        redis_cart = redis_conn.hgetall("carts_%s" % user.id)
        # 查询set中所有的sku_id
        redis_selected = redis_conn.smembers("selected_%s" % user.id)

        # 遍历set中所有的sku_id,通过被勾选的商品的sku_id构造新的被勾选的购物车商品
        new_cart_dict = {} #{"sku_id1": "1"}
        for sku_id in redis_selected:
            new_cart_dict[int(sku_id)] = int(redis_cart[sku_id])

        # 通过被勾选的购物车商品sku_id查询sku
        sku_ids = new_cart_dict.keys()
        skus = SKU.objects.filter(id__in=sku_ids)

        # 遍历skus,累加总件数和总金额
        total_count = 0
        total_amount = Decimal(0.00)
        for sku in skus:
            # 给sku动态绑定商品的数量和小计
            sku.count = new_cart_dict[sku.id]
            sku.amount = sku.price * sku.count

            # 计算总金额结算信息
            total_count += sku.count
            total_amount += sku.amount

        # 准备邮费: 默认没做,所以写的假的
        freight = Decimal("10.00")

        # 构造上下文
        context = {
            "addresses":addresses, # 收货地址
            "skus":skus, # 商品
            'total_count': total_count, # 总件数
            'total_amount': total_amount, # 总金额
            'freight': freight, # 邮费
            'payment_amount': total_amount + freight# 实付款
        }

        # 渲染页面
        return render(request, 'place_order.html',context)